import groovy.text.SimpleTemplateEngine


classPackage = "org.pi.common.matrix"
baseDir = "../../src/"

def matrixClassBindings = [
"Float":	["elementWiseOps":[ // methodname, operator, result 
				["+","+","Float"],
				["-","-","Float"],
				["/","/","Float"],
				["*","*","Float"],
				["<","<","Boolean"],
				[">",">","Boolean"],
				["<=","<=","Boolean"],
				[">=",">=","Boolean"],
				["elementWiseEquals","==","Boolean"]
			],
		"unaryOps":[
				["unary_-","-","Float"]
			],
		],
"Int":     	["elementWiseOps":[
				["+","+","Int"],
				["-","-","Int"],
				["/","/","Int"],
				["*","*","Int"],
				["<","<","Boolean"],
				[">",">","Boolean"],
				["<=","<=","Boolean"],
				[">=",">=","Boolean"],
				["elementWiseEquals","==","Boolean"]
			],
		"unaryOps":[
				["unary_-","-","Int"]
			]
		],
"Boolean": 	["elementWiseOps":[
				["&","&","Boolean"],
				["|","|","Boolean"],
				["^","^","Boolean"],
				["elementWiseEquals","==","Boolean"]
			],
		"unaryOps":[
				["unary_!","!","Boolean"]
			]
		]
]

def matrixClassTemplate = 
'''// automatically generated code
// (c) 2010, Pierre Bayerl
// 
// $fileName
// +--------------------------------
// | classname: $className
// | inner type: $type
// +--------------------------------
//
// time of creation: <% print new Date() %>
//
package $classPackage

<% if (subtype=="Matrix") { %>
/** 
 * ND Matrix class for $type values.
 * values are stored in linear memory, column-wise 
 * (the first dimension is the slowest with position increment 1).
 */
final class $className(paramSize: Int*) extends Matrix[$type]
{
	private val _size = paramSize.toList
	private val _length = _size.foldLeft(1)(_*_) 
	/** public access to inner data. This field is public, so that some methods can be inlined. */
	val data = new Array[$type](length)
	val multidimIndices = new org.pi.common.matrix.indices.DefaultMultidimensionalIndices(shape)

	override final def shape = _size
	override final def length = _length
	override final def length(dim: Int) = _size(dim)


	// ===============================
	// element-wise apply/update
	// ===============================

	@inline
	final override def apply(d1: Int) : $type = data(d1)
	@inline
	final override def apply(d1: Int, d2: Int) : $type = data(multidimIndices(d1,d2))
	@inline
	final override def apply(d1: Int, d2: Int, d3: Int) : $type = data(multidimIndices(d1,d2,d3))

	@inline
	final override def update(d1: Int, value: $type) = { data(d1) = value }
	@inline
	final override def update(d1: Int, d2: Int, value: $type) = { data(multidimIndices(d1,d2)) = value }
	@inline
	final override def update(d1: Int, d2: Int, d3: Int, value: $type) = { data(multidimIndices(d1,d2,d3)) = value }

<% } else if (subtype=="IndexedSubMatrix") { %>
final class $classNameIndexedSubMatrix(val indexSource: IntMatrix, val dataSource: $className) extends Matrix[$type]
{
	val multidimIndices = indexSource.multidimIndices

	final def shape = indexSource.shape
	final def length = indexSource.length
	final def length(dim: Int) = indexSource.length(dim)

	// ===============================
	// element-wise apply/update
	// ===============================

	@inline
	final override def apply(d1: Int) : $type = dataSource(indexSource(d1))
	@inline
	final override def apply(d1: Int, d2: Int) : $type = dataSource(indexSource(d1,d2))
	@inline
	final override def apply(d1: Int, d2: Int, d3: Int) : $type = dataSource(indexSource(d1,d2,d3))

	@inline
	final override def update(d1: Int, value: $type) = { dataSource(indexSource(d1)) = value }
	@inline
	final override def update(d1: Int, d2: Int, value: $type) = { dataSource(indexSource(d1,d2)) = value }
	@inline
	final override def update(d1: Int, d2: Int, d3: Int, value: $type) = { dataSource(indexSource(d1,d2,d3)) = value }

<% } else {
	throw new RuntimeException("unknown subtype "+subtype)
   } %>
	// ===============================
	// misc stuff
	// ===============================

	final def reshape(dims: Int*) = {
		val result = $className(dims :_* )
		result.copyValuesFrom(this)
		result
	}

	/** set all entries to the same value */
	final def setAllValues(value: $type): Unit = {
		var index = 0;
		val n = length
		while(index<n) { 
			update(index, value)
			index += 1
		}
	}

	def trans() : $className = {
		if (multidimIndices.dimCount == 1) {
			val result = new $className(1,length)
			result.copyValuesFrom(this)
			result
		}
		else if (multidimIndices.dimCount == 2) {
			val result = new $className(length(1),length(0))
			var inIndex1 = 0
			var inIndex2 = 0
			val n1 = length(0)
			val n2 = length(1)
			while (inIndex1 < n1) {
				while (inIndex2 < n2) {
					result(inIndex2, inIndex1) = apply(inIndex1, inIndex2)
					inIndex2 += 1
				}
				inIndex1+=1
			}
			result
		}
		else {
			throw new Error("transpose only defined for 1d and 2d")
		}
	}

	/** checks if another matrix has the same size */
	final def hasSameShape(indices: org.pi.common.matrix.indices.MultidimensionalIndices) : Boolean = multidimIndices.equals(indices)

	// ===============================
	// copy and convert methods
	// ===============================

<% for (otherType in otherTypes) {
   otherClassName = allBindings."$otherType".className 
%>
	// convert matrix: $type to $otherType (copy $className -> ${allBindings."$otherType".className}):
	final def to${otherClassName}() : $otherClassName = {
		val result: $otherClassName = new $otherClassName(shape :_* );
		copyTo( result )
		result
	}
<% } %>
<% for (subtypeIndex in [0,1]) {
    for (otherType in allTypes) {
     if (subtypeIndex==0) otherClassName = allBindings."$otherType".className 
     else otherClassName = allBindings."$otherType".classNameIndexedSubMatrix 
%>
	/** copy the values to another matrix. The other matrix must have the same size as this matrix */
	final def copyTo(destination: $otherClassName): Unit = {
		//TODO checksize instead of ...destination.resize( size :_* )
		if (!destination.hasSameShape(this)) throw new Error("shape must match!")
		copyValuesTo(destination)
	}
	/** copy the values to another matrix. The other matrix must have the same number of elements as this matrix */
	final def copyValuesTo(destination: ${otherClassName}): Unit = {
		if (destination.length != length) throw new Error("length must match!")
		var index = 0
		val n = length
		while(index<n) { 
<% if (otherType==type) { %>
			destination(index) = apply(index)
<% } else { %>
<%  if (otherType=="Boolean") { %>
			if (apply(index)==0) destination(index) = false
			else destination(index) = true
<%  } else if (type=="Boolean") { %>
			if (apply(index)) destination(index) = 1.to$otherType
			else destination(index) = 0.to$otherType
<%  } else { %>
			destination(index) = apply(index).to$otherType
<%  } /* if Boolean */ %>
<% } /* if othertype==type */ %>
			index += 1
		}
	}

	/** copy the values from another matrix. The other matrix must have the same number of elements as this matrix */
	final def copyValuesFrom(source: $otherClassName) : Unit = {
		source.copyValuesTo(this)
	}
	/** copy the values from another matrix. The other matrix must have the same size as this matrix */
	final def copyFrom(source: $otherClassName) : Unit = {
		source.copyTo(this)
	}

	/** checks if another matrix has the same size */
	final def hasSameShape(otherMatrix: $otherClassName) : Boolean = otherMatrix.hasSameShape(multidimIndices)

<% }} /* loop (othertype, subtypeindex) */ %>

	/** copy the current matrix */
	def copy() : $className = {
		val result: ${className} = new ${className}(shape :_*);
		copyTo( result )
		result
	}

	// ===============================
	// create submatrices
	// ===============================

	/** get a submatrix which referneces the same memory as
	the original matrix. 
	@param indices indicates a matrix with the zero-based indices from the original matrix to be used as output.
	@return a matrix of the same size of the indices matrix. 
	*/
	final def getReferenceMatrix(indices: IntSelection) : $classNameIndexedSubMatrix = $classNameIndexedSubMatrix(indices.toIntMatrixForGivenStartEndIndices(0,length),this)

	/** get a submatrix which referneces the same memory as
	the original matrix. 
	@param flags indicates the locations of value sin the input matrix to be used 
	(the size of flags mustr match the siz eof this matrix.
	@return a 1D-matrix with sum(flags) elements. 
	*/
	final def getReferenceMatrix(flags: BooleanMatrix) : $classNameIndexedSubMatrix = $classNameIndexedSubMatrix(flags,this)

	/** get a submatrix which referneces the same memory as
	the original matrix. 
	@param indices1 indicates the rows to be used.
	@param indices2 indicates the columns to be used.
	@return a matrix of the size indices1.length x indices2.length. 
	*/
	final def getReferenceMatrix(indices1: IntSelection, indices2: IntSelection) : $classNameIndexedSubMatrix = $classNameIndexedSubMatrix(indices1.toIntMatrixForGivenStartEndIndices(0,length(0)), indices2.toIntMatrixForGivenStartEndIndices(0,length(1)), this)

	/** get a submatrix which referneces the same memory as
	the original matrix. 
	@param indices1 indicates the planes to be used.
	@param indices2 indicates the rows to be used.
	@param indices3 indicates the columns to be used.
	@return a matrix of the size indices1.length x indices2.length x indices3.length. 
	*/
	final def getReferenceMatrix(indices1: IntSelection, indices2: IntSelection, indices3: IntSelection) : $classNameIndexedSubMatrix = $classNameIndexedSubMatrix(indices1.toIntMatrixForGivenStartEndIndices(0,length(0)), indices2.toIntMatrixForGivenStartEndIndices(0,length(1)), indices3.toIntMatrixForGivenStartEndIndices(0,length(2)), this)

	// ===============================
	// apply/update for submatrices (IndexedSubMatrices)
	// ===============================

	/** returns a submatrix. is mapped to getReferenceMatrix. */
	final def apply(indices: IntSelection) : $classNameIndexedSubMatrix = getReferenceMatrix(indices)
	/** returns a submatrix. is mapped to getReferenceMatrix. */
	def apply(flags: BooleanMatrix) : $classNameIndexedSubMatrix = getReferenceMatrix(flags)
	/** returns a submatrix. is mapped to getReferenceMatrix. */
	final def apply(indices1: IntSelection, indices2: IntSelection) : $classNameIndexedSubMatrix = getReferenceMatrix(indices1, indices2)
	/** returns a submatrix. is mapped to getReferenceMatrix. */
	final def apply(indices1: IntSelection, indices2: IntSelection, indices3: IntSelection) : $classNameIndexedSubMatrix = getReferenceMatrix(indices1, indices2, indices3)

	/** set parts of the values */
	final def update(indices: IntSelection, values: $className) : Unit = getReferenceMatrix(indices).copyFrom(values)
	/** set parts of the values */
	final def update(flags: BooleanMatrix, values: $className) : Unit = getReferenceMatrix(flags).copyFrom(values)
	/** set parts of the values */
	final def update(indices1: IntSelection, indices2: IntSelection, values: $className) : Unit = getReferenceMatrix(indices1, indices2).copyFrom(values)
	/** set parts of the values */
	final def update(indices1: IntSelection, indices2: IntSelection, indices3: IntSelection, values: $className) : Unit = getReferenceMatrix(indices1, indices2, indices3).copyFrom(values)

	/** set parts of the values */
	final def update(indices: IntSelection, value: $type) : Unit = getReferenceMatrix(indices).setAllValues(value)
	/** set parts of the values */
	final def update(flags: BooleanMatrix, value: $type) : Unit = getReferenceMatrix(flags).setAllValues(value)
	/** set parts of the values */
	final def update(indices1: IntSelection, indices2: IntSelection, value: $type) : Unit = getReferenceMatrix(indices1, indices2).setAllValues(value)
	/** set parts of the values */
	final def update(indices1: IntSelection, indices2: IntSelection, indices3: IntSelection, value: $type) : Unit = getReferenceMatrix(indices1, indices2, indices3).setAllValues(value)

	// ===============================
	// apply/update for submatrices (RangedSubMatrices)
	// ===============================

	// TODO range!

	// ===============================
	// ElementWiseOps
	// ===============================

<% for (info in elementWiseOps) { 
	methodName = info[0]
	opName = info[1]
	returnType = info[2]+"Matrix"
%>

	/** operator $opName (mapped to method $methodName)
	@return a $returnType */
	final def $methodName(m : $className) : $returnType = {
		if (!hasSameShape(m)) throw new Error("size of matrices must match")
		var result = new $returnType( shape :_* )
		var index: Int = 0
		val n = length
		while (index < n) {
			result(index) = (apply(index) $opName m(index))
			index += 1
		}
		result
	}
	/** operator $opName (mapped to method $methodName)
	@return a $returnType */
	final def $methodName(value : $type) : $returnType = {
		var result = new $returnType( shape :_* )
		var index: Int = 0
		val n = length
		while (index < n) {
			result(index) = (apply(index) $opName value)
			index += 1
		}
		result
	}
<% } /* endloop(ElementWiseOps) */ %>

<% for (info in unaryOps) { 
	methodName = info[0]
	opName = info[1]
	returnType = info[2]+"Matrix"
%>

	/** operator $opName (mapped to method $methodName)
	@return a $returnType */
	final def $methodName : $returnType = {
		var result = new $returnType( shape :_* )
		var index: Int = 0
		val n = length
		while (index < n) {
			result(index) = ($opName apply(index))
			index += 1
		}
		result
	}
<% } /* endloop(UnaryOps) */ %>

	override def toString() = {
		var text = multidimIndices.dimCount match {
			case 1 => "$className(" + multidimIndices.size(0)
			case 2 => "$className(" + multidimIndices.size(0) + " x " + multidimIndices.size(1) 
			case 3 => "$className(" + multidimIndices.size(0) + " x " + multidimIndices.size(1) + " x " + multidimIndices.size(2)
			case _ => throw new Exception("unknown dimension")
		}
		text += ")=[ "
		for (i <- 0 until this.length) {
			if (multidimIndices.dimCount>1 && i%multidimIndices.size(0)==multidimIndices.size(0)-1) text += (this(i)+";\\\\n")
			else text += (this(i)+" ")
		}
		text += "]"
		text
	}  
}

<% if (subtype=="Matrix") { %>
/** companion object */
object $className {
	/** create a $className */
	def apply(shape: Int*) = new $className(shape : _*)

	def apply(data:List[$type]) : $className = {
		val m = $className(data.length)
		var index=0;
		val n = data.length
		while (index<n) {
			m(index) = data(index)
			index += 1
		}
		m
	}
	def apply(data:Array[$type]) : $className = {
		val m = $className(data.length)
		var index=0;
		val n = data.length
		while (index<n) {
			m(index) = data(index)
			index += 1
		}
		m
	}
}
<% } else if (subtype=="IndexedSubMatrix") { %>
/** companion object */
object $classNameIndexedSubMatrix {
	// ===============================
	// additional builders
	// ===============================

	def apply(newIndices: IntMatrix, source: $className) = {
		new $classNameIndexedSubMatrix(newIndices,source)
	}

	def apply(flags: BooleanMatrix, source: $className) = {
		val n = flags.foldLeft(0){ (sum,value) => if (value) sum+1 else sum }
		val newIndices = new IntMatrix(n)
		var index0 = 0
		var index = 0
		val m = flags.length
		while(index0<m) {
			if(flags(index0)) {
				newIndices(index) = index0
				index += 1
			}
			index0 += 1
		}
		new $classNameIndexedSubMatrix(newIndices,source)
	}

	def apply(indices1: IntMatrix, indices2: IntMatrix, source: $className) = {
		val n1 = indices1.length;
		val n2 = indices2.length;
		val newIndices = new IntMatrix(n1,n2)
		var index2 = 0
		var index = 0
		while(index2<n2) {
			var index1 = 0
			while(index1<n1) {
				newIndices(index) = source.multidimIndices(indices1(index1), indices2(index2));
				index += 1
				index1 += 1
			}
			index2 += 1
		}
		new $classNameIndexedSubMatrix(newIndices,source)
	}

	def apply(indices1: IntMatrix, indices2: IntMatrix, indices3: IntMatrix, source: $className) = {
		val n1 = indices1.length;
		val n2 = indices2.length;
		val n3 = indices3.length;
		val newIndices = new IntMatrix(n1,n2,n3)
		var index3 = 0
		var index = 0
		while(index3<n3) {
			var index2 = 0
			while(index2<n2) {
				var index1 = 0
				while(index1<n1) {
					newIndices(index) = source.multidimIndices(indices1(index1), indices2(index2), indices3(index3));
					index += 1
					index1 += 1
				}
				index2 += 1
			}
			index3 += 1
		}
		new $classNameIndexedSubMatrix(newIndices,source)
	}

	def apply(newIndicesParam: IntMatrix, source: $classNameIndexedSubMatrix) = {
		val newIndices = new IntMatrix(newIndicesParam.shape :_*)
		var index = 0
		val n = newIndices.length
		while (index<n) {
			newIndices(index) = source.indexSource(newIndicesParam(index))
			index+=1
		}
		new $classNameIndexedSubMatrix(newIndices,source.dataSource)
	}

	def apply(flags: BooleanMatrix, source: $classNameIndexedSubMatrix) = {
		val n = flags.foldLeft(0){ (sum,value) => if (value) sum+1 else sum }
		val newIndices = new IntMatrix(n)
		var index0 = 0
		var index = 0
		val m = flags.length
		while(index0<m) {
			if(flags(index0)) {
				newIndices(index) = source.indexSource(index0)
				index += 1
			}
			index0 += 1
		}
		new $classNameIndexedSubMatrix(newIndices,source.dataSource)
	}

	def apply(indices1: IntMatrix, indices2: IntMatrix, source: $classNameIndexedSubMatrix) = {
		val n1 = indices1.length;
		val n2 = indices2.length;
		val newIndices = new IntMatrix(n1,n2)
		var index2 = 0
		var index = 0
		while(index2<n2) {
			var index1 = 0
			while(index1<n1) {
				newIndices(index) = source.indexSource(source.multidimIndices(indices1(index1), indices2(index2)));
				index += 1
				index1 += 1
			}
			index2 += 1
		}
		new $classNameIndexedSubMatrix(newIndices,source.dataSource)
	}

	def apply(indices1: IntMatrix, indices2: IntMatrix, indices3: IntMatrix, source: $classNameIndexedSubMatrix) = {
		val n1 = indices1.length;
		val n2 = indices2.length;
		val n3 = indices3.length;
		val newIndices = new IntMatrix(n1,n2,n3)
		var index3 = 0
		var index = 0
		while(index3<n3) {
			var index2 = 0
			while(index2<n2) {
				var index1 = 0
				while(index1<n1) {
					newIndices(index) = source.indexSource(source.multidimIndices(indices1(index1), indices2(index2), indices3(index3)));
					index += 1
					index1 += 1
				}
				index2 += 1
			}
			index3 += 1
		}
		new $classNameIndexedSubMatrix(newIndices,source.dataSource)
	}

}
<% } /* endif */ %>
'''

// enhance binding information
for (type in matrixClassBindings.keySet() ) {
	matrixClassBinding = matrixClassBindings[type] + [
		"type": type, 
		"className": type+"Matrix", 
		"classNameIndexedSubMatrix": type+"IndexedSubMatrix",
		"classNameRangedSubMatrix": type+"RangedSubMatrix",
		"classPackage": classPackage, 
		"allTypes":matrixClassBindings.keySet(), 
		"otherTypes":(matrixClassBindings.keySet()-type) 
	]
	fileName = matrixClassBinding.className + ".scala" 
	matrixClassBinding += ["fileName": fileName]
	matrixClassBinding += ["allBindings":matrixClassBindings]

	matrixClassBindings[type] = matrixClassBinding
}

// generate stuff
for (type in matrixClassBindings.keySet() ) {
	for (subtype in ["Matrix","IndexedSubMatrix"] ) {
		matrixClassBinding = matrixClassBindings[type]
		matrixClassBinding += ["subtype":subtype]
		if (subtype=="Matrix") matrixClassBinding["fileName"] = matrixClassBinding.className + ".scala"
		else if (subtype=="IndexedSubMatrix") matrixClassBinding["fileName"] = matrixClassBinding.classNameIndexedSubMatrix + ".scala"
		else throw new RuntimeException("unknown subtype")

		fileName = matrixClassBinding.fileName
 
		def engine = new SimpleTemplateEngine()
	
		template = engine.createTemplate(matrixClassTemplate).make(matrixClassBinding)

		String code = template.toString()
		dir = baseDir + classPackage.replace(".","/") + "/"
		println dir+fileName
		println "--------------"
		println code
		file = new java.io.File(dir+fileName) 
		file.write(code)
	}
}

